/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package cz.muni.fi.diplomka.datalayer.daoimpl;

import cz.fi.muni.diplomka.common.Army;
import cz.fi.muni.diplomka.common.Coordinates;
import cz.fi.muni.diplomka.common.Squad;
import cz.fi.muni.diplomka.common.Unit;
import cz.fi.muni.diplomka.common.enumeration.MovementType;
import cz.fi.muni.diplomka.common.enumeration.UnitType;
import cz.muni.fi.diplomka.datalayer.dao.MarchingArmyDao;
import cz.muni.fi.diplomka.datalayer.entity.MarchingArmyEntity;
import cz.muni.fi.diplomka.datalayer.util.DbConnector;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.neo4j.cypher.javacompat.ExecutionEngine;
import org.neo4j.cypher.javacompat.ExecutionResult;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.ResourceIterator;
import org.neo4j.graphdb.Transaction;

/**
 *
 * @author Jakub Kutil
 */
public class MarchingArmyDaoImpl implements MarchingArmyDao {

    @Override
    public void addMarchingArmy(MarchingArmyEntity marchingArmyEntity) {
        if (marchingArmyEntity == null) {
            throw new IllegalArgumentException("Marching Army Entity can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            String query = "MATCH (MarchingArmy:MarchingArmy) RETURN max(MarchingArmy.id)";

            Long key = new Long(0);
            ExecutionResult result = engine.execute(query);
            Iterator<Long> number = result.columnAs("max(MarchingArmy.id)");
            while (number.hasNext()) {
                key = number.next();
            }

            if (key == null) {
                key = new Long(0);
            }
            marchingArmyEntity.setId(new Long(key + 1));

            params.put("id", marchingArmyEntity.getId());
            params.put("cityId", marchingArmyEntity.getCityIdFrom());
            params.put("ownerCityId", marchingArmyEntity.getOwnerCityId());
            params.put("name", marchingArmyEntity.getName());
            params.put("xCoordinate", marchingArmyEntity.getCoordinates().getX_coordinate());
            params.put("yCoordinate", marchingArmyEntity.getCoordinates().getY_coordinate());
            params.put("movement", marchingArmyEntity.getMovement().toString());
            params.put("stolenFood", marchingArmyEntity.getStolenFood());
            params.put("stolenGold", marchingArmyEntity.getStolenGold());
            params.put("stolenStone", marchingArmyEntity.getStolenStone());
            params.put("timeOfArrival", marchingArmyEntity.getTimeOfArrival().getTime());
            params.put("returning", marchingArmyEntity.isReturning());

            query = "MATCH (City:City {id :{cityId}}), (OwnerCity:City {id :{ownerCityId}}) "
                    + "CREATE (MarchingArmy:MarchingArmy {id : {id}, name : {name}, xCoordinate : {xCoordinate}, "
                    + "yCoordinate : {yCoordinate}, movement : {movement}, stolenFood : {stolenFood},"
                    + "stolenGold : {stolenGold}, stolenStone : {stolenStone}, timeOfArrival : {timeOfArrival}, "
                    + "returning : {returning}})<-[r:COMMANDS]-(OwnerCity)"
                    + "CREATE (MarchingArmy)<-[r2:FROM]-(City)";

            engine.execute(query, params);
            for (Squad squad : marchingArmyEntity.getArmy().getSquads()) {

                params.put("name", squad.getUnit().getName().toString());
                params.put("position", squad.getPosition());
                params.put("count", squad.getCount());
                params.put("threshold", squad.getThreshold());

                query = "MATCH (MarchingArmy:MarchingArmy {id :{id}})"
                        + "CREATE (MarchingArmyUnits:MarchingArmyUnits {name : {name}, position : {position}, count : {count}"
                        + ", threshold : {threshold}})<-[r:HAS]-(MarchingArmy)";
                engine.execute(query, params);
            }
            t.success();
        }
    }

    @Override
    public void editMarchingArmy(MarchingArmyEntity marchingArmyEntity) {
        if (marchingArmyEntity == null) {
            throw new IllegalArgumentException("Marching Army Entity can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("id", marchingArmyEntity.getId());
            params.put("cityId", marchingArmyEntity.getCityIdFrom());
            params.put("ownerCityId", marchingArmyEntity.getOwnerCityId());
            params.put("name", marchingArmyEntity.getName());
            params.put("xCoordinate", marchingArmyEntity.getCoordinates().getX_coordinate());
            params.put("yCoordinate", marchingArmyEntity.getCoordinates().getY_coordinate());
            params.put("movement", marchingArmyEntity.getMovement().toString());
            params.put("stolenFood", marchingArmyEntity.getStolenFood());
            params.put("stolenGold", marchingArmyEntity.getStolenGold());
            params.put("stolenStone", marchingArmyEntity.getStolenStone());
            params.put("timeOfArrival", marchingArmyEntity.getTimeOfArrival().getTime());
            params.put("returning", marchingArmyEntity.isReturning());

            String query = "MATCH (MarchingArmy:MarchingArmy {id :{id}}) "
                    + "SET MarchingArmy.name = {name}, MarchingArmy.xCoordinate = {xCoordinate}, MarchingArmy.yCoordinate = {yCoordinate}, "
                    + "MarchingArmy.movement = {movement},MarchingArmy.stolenFood = {stolenFood}, MarchingArmy.stolenGold = {stolenGold},"
                    + "MarchingArmy.stolenStone = {stolenStone}, MarchingArmy.timeOfArrival = {timeOfArrival},"
                    + "MarchingArmy.returning = {returning}";
            engine.execute(query, params);

            for (Squad squad : marchingArmyEntity.getArmy().getSquads()) {

                params.put("name", squad.getUnit().getName().toString());
                params.put("position", squad.getPosition());
                params.put("count", squad.getCount());
                params.put("threshold", squad.getThreshold());

                query = "MATCH (MarchingArmy:MarchingArmy {id :{id}})-[r:HAS]->(MarchingArmyUnits:MarchingArmyUnits { position : {position}}) "
                        + "SET MarchingArmyUnits.name = {name}, MarchingArmyUnits.count = {count}, MarchingArmyUnits.threshold= {threshold}";
                engine.execute(query, params);
            }
            t.success();
        }
    }

    @Override
    public void deleteMarchingArmy(Long id) {
        if (id == null) {
            throw new IllegalArgumentException("Id can not be null.");
        }

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();

        try (Transaction t = gds.beginTx()) {

            Map<String, Object> params = new HashMap<>();

            params.put("id", id);

            String query = "MATCH ()-[r]-(MarchingArmy:MarchingArmy{ id : {id}})-[r2:HAS]->(MarchingArmyUnits:MarchingArmyUnits)"
                    + "DELETE r,r2,MarchingArmyUnits,MarchingArmy";
            engine.execute(query, params);

            t.success();
        }
    }

    @Override
    public List<MarchingArmyEntity> getAllMarchingArmiesOfCity(Long cityId) {
        if (cityId == null) {
            throw new IllegalArgumentException("City Id can not be null.");
        }

        List<MarchingArmyEntity> marchingArmyList = new ArrayList<>();

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            params.put("id", cityId);

            String query = "MATCH (City:City {id : {id}})-[r:FROM]->(MarchingArmy:MarchingArmy)<-[r2:COMMANDS]-(OwnerCity:City)"
                    + "RETURN MarchingArmy,OwnerCity.id";

            ExecutionResult result = engine.execute(query, params);

            ResourceIterator<Map<String, Object>> armies = result.iterator();

            while (armies.hasNext()) {
                Map<String, Object> row = armies.next();
                Node node = (Node) row.get("MarchingArmy");

                MarchingArmyEntity marchingArmyEntity = new MarchingArmyEntity();
                marchingArmyEntity.setId(new Long(node.getProperty("id").toString()));
                marchingArmyEntity.setOwnerCityId(new Long(row.get("OwnerCity.id").toString()));
                marchingArmyEntity.setCityIdFrom(cityId);
                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                marchingArmyEntity.setCoordinates(c);
                marchingArmyEntity.setMovement(MovementType.valueOf(node.getProperty("movement").toString()));
                marchingArmyEntity.setName(node.getProperty("name").toString());
                marchingArmyEntity.setReturning(Boolean.valueOf(node.getProperty("returning").toString()));
                marchingArmyEntity.setStolenFood(Integer.valueOf(node.getProperty("stolenFood").toString()));
                marchingArmyEntity.setStolenGold(Integer.valueOf(node.getProperty("stolenGold").toString()));
                marchingArmyEntity.setStolenStone(Integer.valueOf(node.getProperty("stolenStone").toString()));
                marchingArmyEntity.setTimeOfArrival(new Timestamp(Long.valueOf(node.getProperty("timeOfArrival").toString())));

                marchingArmyList.add(marchingArmyEntity);
            }
            for (MarchingArmyEntity marchingArmyEntity : marchingArmyList) {
                List<Squad> squads = new ArrayList<>();
                params.clear();
                params.put("id", marchingArmyEntity.getId());

                query = "MATCH (MarchingArmy:MarchingArmy {id : {id}})-[r:HAS]->(MarchingArmyUnits:MarchingArmyUnits)"
                        + "RETURN MarchingArmyUnits";

                result = engine.execute(query, params);

                Iterator<Node> units = result.columnAs("MarchingArmyUnits");
                while (units.hasNext()) {
                    Node node = units.next();
                    Squad squad = new Squad();
                    squad.setCount(Float.valueOf(node.getProperty("count").toString()));
                    squad.setPosition(Integer.valueOf(node.getProperty("position").toString()));
                    squad.setThreshold(Float.valueOf(node.getProperty("threshold").toString()));
                    Unit unit = new Unit();
                    unit.setName(UnitType.valueOf(node.getProperty("name").toString()));
                    squad.setUnit(unit);

                    squads.add(squad);
                }
                Army armada = new Army();
                armada.setSquads(squads);
                marchingArmyEntity.setArmy(armada);
            }
            t.success();
        }
        return marchingArmyList;
    }

    @Override
    public List<MarchingArmyEntity> getAllMarchingArmies() {
        List<MarchingArmyEntity> marchingArmyList = new ArrayList<>();

        GraphDatabaseService gds = DbConnector.getDBConnection();
        ExecutionEngine engine = DbConnector.getEngine();


        try (Transaction t = gds.beginTx()) {
            Map<String, Object> params = new HashMap<>();

            String query = "MATCH (City:City)-[r:FROM]->(MarchingArmy:MarchingArmy)<-[r2:COMMANDS]-(OwnerCity:City)"
                    + "RETURN MarchingArmy,City.id,OwnerCity.id";

            ExecutionResult result = engine.execute(query, params);

            ResourceIterator<Map<String, Object>> armies = result.iterator();

            while (armies.hasNext()) {
                Map<String, Object> row = armies.next();
                Node node = (Node) row.get("MarchingArmy");

                MarchingArmyEntity marchingArmyEntity = new MarchingArmyEntity();
                marchingArmyEntity.setId(new Long(node.getProperty("id").toString()));
                marchingArmyEntity.setOwnerCityId(new Long(row.get("OwnerCity.id").toString()));
                marchingArmyEntity.setCityIdFrom(new Long(row.get("City.id").toString()));
                Coordinates c = new Coordinates();
                c.setX_coordinate(Integer.valueOf(node.getProperty("xCoordinate").toString()));
                c.setY_coordinate(Integer.valueOf(node.getProperty("yCoordinate").toString()));
                marchingArmyEntity.setCoordinates(c);
                marchingArmyEntity.setMovement(MovementType.valueOf(node.getProperty("movement").toString()));
                marchingArmyEntity.setName(node.getProperty("name").toString());
                marchingArmyEntity.setReturning(Boolean.valueOf(node.getProperty("returning").toString()));
                marchingArmyEntity.setStolenFood(Integer.valueOf(node.getProperty("stolenFood").toString()));
                marchingArmyEntity.setStolenGold(Integer.valueOf(node.getProperty("stolenGold").toString()));
                marchingArmyEntity.setStolenStone(Integer.valueOf(node.getProperty("stolenStone").toString()));
                marchingArmyEntity.setTimeOfArrival(new Timestamp(Long.valueOf(node.getProperty("timeOfArrival").toString())));

                marchingArmyList.add(marchingArmyEntity);
            }
            for (MarchingArmyEntity marchingArmyEntity : marchingArmyList) {
                List<Squad> squads = new ArrayList<>();
                params.clear();
                params.put("id", marchingArmyEntity.getId());

                query = "MATCH (MarchingArmy:MarchingArmy {id : {id}})-[r:HAS]->(MarchingArmyUnits:MarchingArmyUnits)"
                        + "RETURN MarchingArmyUnits";

                result = engine.execute(query, params);

                Iterator<Node> units = result.columnAs("MarchingArmyUnits");
                while (units.hasNext()) {
                    Node node = units.next();
                    Squad squad = new Squad();
                    squad.setCount(Float.valueOf(node.getProperty("count").toString()));
                    squad.setPosition(Integer.valueOf(node.getProperty("position").toString()));
                    squad.setThreshold(Float.valueOf(node.getProperty("threshold").toString()));
                    Unit unit = new Unit();
                    unit.setName(UnitType.valueOf(node.getProperty("name").toString()));
                    squad.setUnit(unit);

                    squads.add(squad);
                }
                Army armada = new Army();
                armada.setSquads(squads);
                marchingArmyEntity.setArmy(armada);
            }
            t.success();
        }
        return marchingArmyList;
    }
}
